import sys
import cPickle
import os
import time
import string
import types
import socket
import traceback
import imp
import cPickle
import fpformat

import jsonpickle

try:
	import json
except ImportError:
	import simplejson as json

import config
import constants

from octopusdbm.client import Dbm
from bsddb3 import db

"""
    Copyright (C) 2009, 2010  Jorge Alegre Vilches

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
    
"""

class Service(object):
	"""So far this class just takes class and method arguments. callMethod will always return a list. We will have further
	methods or optional arguments for output formating like JSON, XML, etc..."""
	_oService = None
	_ClassName = ''
	_Pkg = ''
	def __init__(self, sClassPath, *ArgsTuple, **ArgsDict):
		"""@param sClassName: Class name with full path, like x.y.z.Class
		@param ArgsTuple: Any arguments received
		@param ArgsDict: Any optional arguments as a dictionary"""
		self._ClassName = ''
		self._Pkg = ''
		self._oService = None
		if sClassPath.find('.') != -1:
			Fields = sClassPath.split('.')
			self._ClassName = Fields[len(Fields)-1]
			#self._Pkg = Fields[len(Fields)-2]
			sPkg = Fields[len(Fields)-2]
			i = 0
			sPath = None
			for sField in Fields:
				if i != (len(Fields)-2) and i != (len(Fields)-1) and sPath == None:
					TupleInfo = imp.find_module(sField)
					sPath = TupleInfo[1]
				elif i != (len(Fields)-2) and i != (len(Fields)-1) and sPath != None:
					TupleInfo = imp.find_module(sField, [sPath])
				i += 1
			# Tuple with last info module for path
			sPath = TupleInfo[1]
			Tuple = imp.find_module(sPkg, [sPath])
			Pkg = imp.load_module(sPkg, Tuple[0], Tuple[1], Tuple[2])
			self._Pkg = Pkg
		# buscaplus.util.config.Dbmd
		self._oService = eval('Pkg' + '.' + self._ClassName)(*ArgsTuple, **ArgsDict)
	def _log(self, sClass, sMethod, sTxt):
		"""Doc."""
		f = open(self._PATH_LOG, 'a')
		timeTuple = time.localtime(time.time())
		sTime = time.strftime('[%d/%b/%Y %H:%M:%S]', timeTuple)
		f.write(sTime + ' ' + sClass + '.' + sMethod + '() : ' + sTxt + '\015\012')
		f.close()
	def _formatTime(self, t_1, t_2):
		"""Doc."""
		timeTuple = time.localtime()
		t1 = fpformat.fix(t_1-time.mktime((timeTuple[0],timeTuple[1],timeTuple[2],timeTuple[3],timeTuple[4],0,0,0,0)), 2)
		t2 = fpformat.fix(t_2-time.mktime((timeTuple[0],timeTuple[1],timeTuple[2],timeTuple[3],timeTuple[4],0,0,0,0)), 2)
		td = fpformat.fix(t_2-t_1, 4)
		showTime = t1 + '-' + t2 + '(' + td + ')'
		return showTime
	def call(self, sMethodName, *ArgsTuple, **ArgsDict):
		"""@param sMethodName: Method name
		@param ArgsTuple: Tuple of arguments for method
		@param ArgsDict: Optional arguments for method as dictionary
		@return: result : Result can have any type, being an instance, a list, tuple, string, int, etc..."""
		instance = self._oService
		result = eval('instance' + '.' + sMethodName)(*ArgsTuple, **ArgsDict)
		return result

class ServiceCloud(Service):
	_Host = ''
	_WorkId = -1
	_DEBUG = False
	_PATH_LOG = ''
	_Method = ''
	MAX_TRIES = 30
	def __init__(self, sClassPath, bDebug, sPathLog, *ArgsTuple, **ArgsDict):
		"""Constructor for service cloud. Instantiates the parent class Service with ClassPath. Takes arguments and
		keyword arguments that the class may take.
		@param sClassPath : String: Full class path, like module.package.Class, or module.Class"""
		super(ServiceCloud, self).__init__(sClassPath, *ArgsTuple, **ArgsDict)
		self._DEBUG = bDebug
		self._PATH_LOG = sPathLog
		self._Method = ''
	def callMap(self, sMethodName, sDbMapHost, sWorkId, iOrder, *ArgsTuple, **ArgsDict):
		"""Process method and writes result in a table for this piece. Takes any argument or keyword argument that the class may take.
		@param sMethodName: String : Method name
		@param sDbMapHost: String : Host used to write data for this piece.
		@param sWorkId : Integer: Work id, common to all pieces
		@param iOrder : Integer : Order used for later collecting data"""
		self._Method = sMethodName
		if self._DEBUG:
			t_1 = time.time()
		self._WorkId = sWorkId
		instance = self._oService
		result = eval('instance' + '.' + sMethodName)(*ArgsTuple, **ArgsDict)
		if self._DEBUG:
			t_12 = time.time()
		tupleResult = (iOrder, result)
		resultS = json.dumps(tupleResult)
		if self._DEBUG:
			t_13 = time.time()
		dbm = Dbm(sDbMapHost, config.OctopusDict['user'], config.OctopusDict['password'], config.OctopusDict['environment'])
		dbm.set_flags(db.DB_DUP)
		dbm.putDirect(constants.TABLE_COLLECT, str(sWorkId), resultS, db.DB_CREATE, db.DB_BTREE)
		if self._DEBUG:
			t_2 = time.time()
			sTxt = 'Time: Method:' + self._formatTime(t_1, t_12) + 'sc JSON:' + self._formatTime(t_12, t_13) + 'sc Db:' + self._formatTime(t_13, t_2) + ' sc Total:' + self._formatTime(t_1, t_2)			
			self._log(self._ClassName, sMethodName, sTxt)
	def collect(self, sDbMapHost, sWorkId, iNumberWorks):
		"""Collects data for number of sent requests iNumberWorks.
		@param iNumberWorks : Integer: Number of requests
		@return: result : Any Type : Results ordered and put together from all pieces in database. Later on data saved
		is deleted from table for selected host in previous methods."""
		if self._DEBUG:
			t_1 = time.time()
		dbm = Dbm(sDbMapHost, config.OctopusDict['user'], config.OctopusDict['password'], config.OctopusDict['environment'])
		dbm.open(constants.TABLE_COLLECT, db.DB_CREATE, db.DB_BTREE, sMode='w')
		c = dbm.cursor()		
		i = 0
		# !!!!!!!!!!!!! Go to constants
		MAX_TIME = 10
		counter = 0
		while 1:
			if i >= self.MAX_TRIES:
				break
			list = c.firstDupList(str(sWorkId), iNumberWorks)
			#t_2 = time.time()
			if len(list) == iNumberWorks:
				break
			"""if t_2-t_1 > MAX_TIME:
				break"""
			time.sleep(0.05)
			i += 1
		if dbm.has_key(str(sWorkId)):
			dbm.delete(str(sWorkId))
		c.close()
		dbm.close()
		result = []
		tupleList = []
		for tupleResult in list:
			resultS = tupleResult[1]
			tuple = json.loads(resultS)
			tupleList.append(tuple)
		tupleList.sort()
		for tuple in tupleList:
			resultPiece = tuple[1]
			if type(resultPiece) == types.ListType or type(resultPiece) == types.TupleType:
				for resultFieldPiece in resultPiece:
					result.append(resultFieldPiece)
			else:
				result.append(resultPiece)
		if self._DEBUG:
			t_2 = time.time()
			self._log(self._ClassName, self._Method, ' Time Collect: ' + fpformat.fix(t_2-t_1, 3) + 'sc')		
		return result

class ServiceHandler:
	_PATH_ERROR = ''
	_PATH_LOG = ''
	_DEBUG = False
	def __init__(self, request, client_address, server):
		"""Service handler used by SocketServer."""
		self.request = request
		self.client_address = client_address
		self.server = server
		self.allow_reuse_address = 1
		self._PATH_ERROR = self.server.DataDict['PATH_ERROR']
		self._PATH_LOG = self.server.DataDict['PATH_LOG']
		self._DEBUG = self.server.DataDict['DEBUG']
		try:
			self.setup()
			self.handle()
			self.finish()
		finally:
			sys.exc_traceback = None    # Help garbage collection
	def setup(self):
		"""Doc."""
		pass
	def handle(self):
		"""Handles the request and sends response serialized in jsonpickle format as dictionary
		@return: ResponseDict : Having keys bResult and Result. ReponseDict is serialized and send in the socket connection. 
		Then socket is closed and error files closed as well."""		
		fd = open(self._PATH_ERROR, 'a')
		sys.stderr = fd
		SocketRequest = self.request
		DataList = []
		returnCode = 'ok'
		BUFFER = 16384
		#BUFFER = 64
		bValidation = False
		while 1:
			data = SocketRequest.recv(BUFFER)
			DataList.append(data)
			try:
				SDict = string.joinfields(DataList, '')
				Dict = jsonpickle.decode(SDict)
				break
			except EOFError:
				pass
			except socket.error:
				returnCode = 'error'
			except ValueError:
				pass
		RequestDict = Dict
		#print 'RequestDict => ', RequestDict
		ResponseDict = {}
		bError = False
		try:
			ClassArgsTuple = RequestDict[u'ClassArgsTuple']
			ClassArgsDict = RequestDict[u'ClassArgsDict']
			ArgsTuple = RequestDict[u'ArgsTuple']
			ArgsDict = RequestDict[u'ArgsDict']
			if not RequestDict.has_key('Op'):
				#print 'simple call...'
				oService = Service(RequestDict[u'ClassPath'], *ClassArgsTuple, **ClassArgsDict)
				result = oService.call(RequestDict[u'Method'], *ArgsTuple, **ArgsDict)
			elif RequestDict.has_key('Op') and RequestDict['Op'] == 'callMap':
				#print 'callMap() ...'
				sDbMapHost = RequestDict[u'DbMapHost']
				sWorkId = RequestDict[u'WorkId']
				iOrder = RequestDict[u'Order']
				oService = ServiceCloud(RequestDict[u'ClassPath'], self._DEBUG, self._PATH_LOG, *ClassArgsTuple, **ClassArgsDict)
				oService.callMap(RequestDict[u'Method'], sDbMapHost, sWorkId, iOrder, *ArgsTuple, **ArgsDict)
			elif RequestDict.has_key('Op') and RequestDict['Op'] == 'collect':
				#print 'collect() ...'
				sDbMapHost = RequestDict[u'DbMapHost']
				sWorkId = RequestDict[u'WorkId']
				oService = ServiceCloud(RequestDict[u'ClassPath'], self._DEBUG, self._PATH_LOG, *ClassArgsTuple, **ClassArgsDict)
				iNumberWorks = RequestDict[u'NumberRequests']
				result = oService.collect(sDbMapHost, sWorkId, iNumberWorks)
		except:
			bError = True
			sError = traceback.format_exc()
			# Write to error log
			sDate = time.strftime('%d/%m/%Y %H:%M')
			fd.write('[' + sDate + ']' + sError)
		# ResponseDict
		doResponse = True
		if RequestDict.has_key(u'bResponse'):
			bResponse = RequestDict[u'bResponse']
			if bResponse:
				doResponse = True
			else:
				doResponse = False
		if doResponse:
			if not bError:
				ResponseDict[u'Result'] = result
				ResponseDict[u'bResult'] = True
				ResponseDict[u'sError'] = ''
			else:
				ResponseDict[u'Result'] = ''
				ResponseDict[u'bResult'] = False
				ResponseDict[u'sError'] = sError
			#print 'ResponseDict => ', ResponseDict
			sResponseDict = jsonpickle.encode(ResponseDict)
			SocketRequest.sendall(sResponseDict)
		SocketRequest.close()
		fd.close()		
	def finish(self):
		"""Doc."""
		pass
